package com.tarena.lbs.article.web.service;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.tarena.lbs.article.web.dao.repository.ActionESRepository;
import com.tarena.lbs.article.web.dao.repository.ActionRepository;
import com.tarena.lbs.article.web.dao.repository.ArticleRepository;
import com.tarena.lbs.article.web.thread.AuthenticationUtil;
import com.tarena.lbs.attach.api.AttachApi;
import com.tarena.lbs.base.common.utils.Asserts;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.content.utils.SequenceGenerator;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.marketing.api.MarketingApi;
import com.tarena.lbs.pojo.attach.param.PicUpdateParam;
import com.tarena.lbs.pojo.content.entity.ActionSearchEntity;
import com.tarena.lbs.pojo.content.entity.ArticleSearchEntity;
import com.tarena.lbs.pojo.content.param.ArticleActionParam;
import com.tarena.lbs.pojo.content.param.ArticleContentParam;
import com.tarena.lbs.pojo.content.query.ArticleQuery;
import com.tarena.lbs.pojo.content.vo.ArticleActionPageResultVO;
import com.tarena.lbs.pojo.content.vo.ArticleActionVO;
import com.tarena.lbs.pojo.content.vo.ArticleVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ArticleService {
    @Autowired
    private ArticleRepository articleRepository;
    @Autowired
    private ActionRepository actionRepository;
    /**
     * 将条件 组织封装 判断放到业务层 形成一个 query对象
     * 仓储层根据query非空拼接bool查询es的文章数据
     * @param articleQuery
     * @return
     */
    public PageResult<ArticleVO> articlePage(ArticleQuery articleQuery) throws BusinessException {
        //1.准备一个返回的分页数据
        PageResult<ArticleVO> voPage=assemblePageResult();
        //2.source来源的值 判断 必须不能为空 应该可以使用validation校验@Validate
        Integer source=articleQuery.getSource();
        //3.根据不同分支 组织一个新的查询条件query
        ArticleQuery repositoryQuery=new ArticleQuery();
        if (source==1){
            //查询来源 手机小程序
            //地理位置  lat lon
            repositoryQuery.setLatitude(articleQuery.getLatitude());
            repositoryQuery.setLongitude(articleQuery.getLongitude());
            //标签 查询
            repositoryQuery.setArticleLabel(articleQuery.getArticleLabel());
            //前台查询的文章必须只能是上线的文章不能是草稿
            repositoryQuery.setArticleStatus(CollectionUtils.list(1));
        }else{
            //查询来源是后台 articleTitle articleCategory status 0|草稿 1|发布的文章
            //activityId绑定的活动 startTime endTime userId(文章作者)
            BeanUtils.copyProperties(articleQuery,repositoryQuery);
            repositoryQuery.setUserId(getUserId());
        }
        //4.调用仓储层 获取所有满足repositoryQuery条件的文章列表
        List<ArticleSearchEntity> entities=articleRepository.search(repositoryQuery);
        //5.给分页对象 装配一下vos
        List<ArticleVO> vos=null;
        if (CollectionUtils.isNotEmpty(entities)){
            vos=entities.stream().map(entity->{
                ArticleVO vo=new ArticleVO();
                BeanUtils.copyProperties(entity,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }

    private Integer getUserId() throws BusinessException {
        UserPrinciple userPrinciple = AuthenticationUtil.get();
        Asserts.isTrue(userPrinciple== null, new BusinessException("-2","认证解析失败"));
        return userPrinciple.getId();
    }

    private PageResult<ArticleVO> assemblePageResult() {
        PageResult<ArticleVO> voPage=new PageResult<>();
        voPage.setPageNo(1);
        voPage.setPageSize(10);
        voPage.setTotal(100L);
        return voPage;
    }

    public void addArticle(ArticleContentParam articleContentParam) throws BusinessException {
        //1.param转成 entity 写入到es中
        ArticleSearchEntity entity=param2entity(articleContentParam);
        articleRepository.save(entity);
        //2.绑定图片 如果不绑定 XXL-JOB定时任务会定时删除,文章过一段时间就没图片了
        bindPictures(articleContentParam,entity.getId());//封面500 内容 600
    }
    @DubboReference
    private AttachApi attachApi;
    private void bindPictures(ArticleContentParam param, Integer businessId) {
        //每次绑定 都可能是多个图片
        List<PicUpdateParam> pics=new ArrayList<>();
        //1.文章封面 bizType=500 bizId=businessId
        String coverUrl = param.getArticleCover();
        String coverfileUuid=parseUrlFileUuid(coverUrl);
        PicUpdateParam coverParam=new PicUpdateParam();
        coverParam.setBusinessType(500);
        coverParam.setBusinessId(businessId);
        coverParam.setFileUuid(coverfileUuid);
        pics.add(coverParam);
        //2.文章内容的图片绑定 url1,url2,url3,... bizType=600
        String articleMain = param.getArticleMainPic();
        String[] mainUrls = articleMain.split(",");
        List<PicUpdateParam> mainPics=Arrays.stream(mainUrls).map(url->{
            PicUpdateParam picParam=new PicUpdateParam();
            picParam.setBusinessType(600);
            picParam.setBusinessId(businessId);
            picParam.setFileUuid(parseUrlFileUuid(url));
            return picParam;
        }).collect(Collectors.toList());
        pics.addAll(mainPics);
        attachApi.batchUpdateBusiness(pics);
    }

    private String parseUrlFileUuid(String coverUrl) {
        //http://localhost:9081/static/aofeo.png
        return coverUrl.split("/")[4];
    }


    private ArticleSearchEntity param2entity(ArticleContentParam param) throws BusinessException {
        ArticleSearchEntity entity=new ArticleSearchEntity();
        //1.能拷贝的 都拷贝
        BeanUtils.copyProperties(param,entity);
        //2.文章的作者 userId
        UserPrinciple userPrinciple = AuthenticationUtil.get();
        Asserts.isTrue(userPrinciple== null, new BusinessException("-2","认证解析失败"));
        entity.setUserId(userPrinciple.getId());
        //3. 缺少 id 模拟了一个序列化器 生成的Integer数值
        Integer sequence = SequenceGenerator.sequence();//唯一 递增趋势 格式固定(64个字符,32个字符)
        entity.setId(sequence);
        //4. 时间数据 createTime updateTime
        Date now=new Date();
        entity.setCreateTime(now);
        entity.setUpdateTime(now);
        //5. 地理位置调整格式 location = latitude+","+longitude
        entity.setLocation(param.getLatitude()+","+param.getLongitude());
        //6. 状态判断如果空 自动填充一个0 草稿状态
        if (param.getArticleStatus()==null){
            entity.setArticleStatus(0);
        }
        //7. accessAccount likeAccount favoriteAccount 三个值初始化 后续使用redis的hash 保存数据
        entity.setAccessCount(0);//点击次数
        entity.setLikeCount(0);//点赞次数
        entity.setFavoriteCount(0);//收藏次数
        return entity;
    }

    public HashSet<String> articleLabels(ArticleQuery articleQuery) {
        //1.将查询条件 交给仓储层,经过定位查询 去重处理 给我返回一个文章对象集合
        List<ArticleSearchEntity> entities=articleRepository.getArticleLabelsByLocation(articleQuery);
        //2.拿到 每一个返回值对象的 标签 封装set集合
        HashSet<String> labels=new HashSet<>();
        if (CollectionUtils.isNotEmpty(entities)){
            entities.forEach(entity->{
                labels.add(entity.getArticleLabel());
            });
        }
        return labels;
    }
    @DubboReference
    private MarketingApi marketingApi;
    public ArticleVO detail(Integer id) throws BusinessException {
        ArticleSearchEntity entity=articleRepository.getArticleById(id);
        Asserts.isTrue(entity==null,new BusinessException("-2","文章不存在"));
        //1.判断当前entity数据 中是否存在活动id
        Integer activityId = entity.getActivityId();
        if (activityId!=null){
            log.info("当前文章 有活动的id:{},需要判断是否给用户展示",activityId);
            //2.远程调用 判断 当前用户是否可以看到activityId
            Integer userId = getUserId();
            //远程调用marketing服务接口,最终决定entity中的活动id是否屏蔽掉
            Boolean visibal = marketingApi.activityIsVisibal(userId, activityId);
            if (!visibal){
                log.info("当前用户:{},不符合目标人群,活动ID:{} 不予展示",userId,activityId);
                entity.setActivityId(null);
            }
        }
        ArticleVO vo=new ArticleVO();
        BeanUtils.copyProperties(entity,vo);

        //3.记录当前 访问的用户 对文章进行了access访问操作 lbs_action actionSearchEntity 写入到索引
        saveUserAction(vo,3);
        //4.更新文章的 accessCount的值
        updateBehaviorCount(vo,3);
        log.info("当前文章对象:{}",vo);
        return vo;
    }
    @Autowired
    private StringRedisTemplate redisTemplate;
    private void updateBehaviorCount(ArticleVO vo, Integer behavior) {
        //TODO文章中 有likeCount favoriteCount accessCount 用户行为不同 更新不同的数字
        //提示: 以下代码的理解前提是 明白 我们使用的hash数据结构 存储的一个文章的计数数据
        //key值 对应的是一个文章 value值 保存了这篇文章的 所有行为数值1,2,3,4,5 和对应计数次数
        HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
        String artickeCountKey="article:count:"+vo.getId();
        //所有redis中类型的数字计数器 都是从0开始计算的
        opsForHash.increment(artickeCountKey, behavior + "", 1);
        //给vo 回填 将所有的值 都从redis查询出来 给vo回填
        String likeCount = opsForHash.get(artickeCountKey, "1");
        String favoriteCount = opsForHash.get(artickeCountKey, "2");
        String accessCount = opsForHash.get(artickeCountKey, "3");
        vo.setAccessCount(Integer.parseInt(accessCount==null?"0":accessCount));
        vo.setFavoriteCount(Integer.parseInt(favoriteCount==null?"0":favoriteCount));
        vo.setLikeCount(Integer.parseInt(likeCount==null?"0":likeCount));
    }

    @Autowired
    private ActionESRepository actionESRepository;
    private void saveUserAction(ArticleVO vo,Integer behavior) throws BusinessException {
        //1.组织一个entity对象调用 save到索引
        ActionSearchEntity entity=new ActionSearchEntity();
        //2.当前请求的认证对象
        Integer userId = getUserId();
        //3.需要保存文章一些冗余数据 由于字段属性不匹配 beanUtils拷贝 换成手动封装
        entity.setArticleTitle(vo.getArticleTitle());
        entity.setArticleType(vo.getArticleCategory());
        entity.setArticleLabel(vo.getArticleLabel());
        entity.setArticleId(vo.getId());
        entity.setArticleUserId(vo.getUserId());
        //文章id 文章作者 文章标题 文章类型 文章标签
        //4.行为数据自己的记录
        //如果是评论 有comment 行为类型 behavior 1 2 3 4 5... 创建时间
        entity.setBehaviorUserId(userId);
        entity.setBehavior(behavior);
        //如果 本次行为是4 应为给行为记录 添加评论内容
        log.info("当前文章行为记录:{}",entity );
        actionESRepository.save(entity);
    }

    public void behavior(ArticleActionParam param) throws BusinessException {
        //1.发起请求的人
        //Integer userId = getUserId();
        //2.拿到文章详情
        ArticleSearchEntity articleEntity = articleRepository.getArticleById(param.getId());
        Asserts.isTrue(articleEntity==null,new BusinessException("-2","文章不存在"));
        //3.我了复用之前 新增用户行为 将articleEntity 解析成vo
        ArticleVO vo=new ArticleVO();
        BeanUtils.copyProperties(articleEntity,vo);
        //4.新增行为
        saveUserAction(vo,param.getBehavior());
        //5.更新 计数数字
        updateBehaviorCount(vo,param.getBehavior());
    }

    public ArticleActionPageResultVO getActionList(ArticleQuery query) throws BusinessException {
        //1.解析认证 拿到userId
        Integer userId = getUserId();
        //2.第一个版本代码的执行时间 统计一下
        Long start=System.currentTimeMillis();
        //3.在lbs_action存储的文章 行为动作值 1|2|3都要查
        //3.1先查询 behavior=1 点赞过的文章 同一篇文章 可以有多次点赞在action 查到的文章id 去重
        PageResult<ArticleActionVO> likeVos=selectByBehavior(1,userId);
        //3.2查询 behavior=2 收藏过的文章 一篇文章 可以多次点击收藏 记录多次数据
        PageResult<ArticleActionVO> collectVos=selectByBehavior(2,userId);
        //3.behavior=3 曾经访问过的文章
        PageResult<ArticleActionVO> accessVos=selectByBehavior(3,userId);
        Long end=System.currentTimeMillis();
        log.info("查询当前用户:{},交互的所有文章耗时:{}MS",userId,end-start);
        //4 vo返回值依次封装数据
        ArticleActionPageResultVO vo=new ArticleActionPageResultVO();
        vo.setLikeFuturePageResult(likeVos);
        vo.setCollectFutureOPageResult(collectVos);
        vo.setCommentFuturePageResult(accessVos);
        return vo;
    }

    private PageResult<ArticleActionVO> selectByBehavior(Integer behavior, Integer userId) {
        //sql代替 select * from lbs_action where behave_user_id=#{} and behavior=#{}
        //1.准备一个分页返回值 缺少分页list封装到es查询
        PageResult<ArticleActionVO> voPage=new PageResult<>(1,10);
        voPage.setTotal(100L);
        //2.将behavior和userId交给仓储层 到lbs_action索引查询list数据
        List<ActionSearchEntity> actionArticles=actionRepository.getActionAticles(behavior,userId);
        List<ArticleActionVO> vos=null;
        if (CollectionUtils.isNotEmpty(actionArticles)){
            vos=actionArticles.stream().map(entity->{
                ArticleActionVO vo=new ArticleActionVO();
                BeanUtils.copyProperties(entity,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }
}
